/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID coidgen tool.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Robo Strycek
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */



#include <stdio.h>
#include <stdarg.h>
#include <sys/stat.h>
#include "coid/comm/commassert.h"

#include "coid/comm/binstream/filestream.h"
#include "coid/comm/binstream/binstreambuf.h"
#include "coid/comm/dir.h"

#include "coidgen.h"
#include "os.h"
#include "ver.h"





extern char TEMPL_DISPATCH_CPP[];
extern char TEMPL_CLIENT_CPP[];
extern char TEMPL_CLIENT_H[];
extern char TEMPL_MAKEFILE[];




const char * g_source_header_path = NULL;
const char * g_source_dev = NULL;
charstr g_author, g_source_header_name0;
charstr OUTPUT_CLIENT_H, OUTPUT_CLIENT_CPP, OUTPUT_DISPATCH_CPP;






void write_bin_dispatch_data( charstr & out, H_FILE & h_client, const char * ver_coidgen, const char * ver_coid,
								size_t source_size, uint source_time );
int read_bin_dispatch_data( binstream& from, bool binary, H_FILE & h, const char * current_coidgen_ver, charstr & ver_coidgen, charstr & ver_coid,
								size_t * source_size, uint* source_time );





int help()
{
	printf( "USAGE: coidgen [-f] file_name.h [file_name.dev]\n" );
	return 1;
}


int print_version()
{
	charstr tmp = "coidgen version  ";
	tmp += STR_COIDGEN_VERSION;
	printf( "%s\n", tmp.ptr() );
	return 0;
}


FILE * rename_and_open_file_for_writing( const charstr & filename )
{
    if( directory::is_valid_file(filename) )
    {
        charstr tmp = filename;
        tmp += ".old";
        directory::delete_file(tmp);
        directory::move_file( filename, tmp );
	}

    FILE* f = ::fopen( filename.ptr(), "wb" );
	if(!f) printf( "Cannot open file '%s' for writing\n", filename.ptr() );


	return f;
}





int main(int argc, char* argv[])
{
	bool b_force_generate = false;		/// -f
	bool b_keep_major = false;			/// -m
	bool b_keep_minor = false;			/// -n
	bool b_force_major = false;			/// -M
	bool b_force_minor = false;			/// -N

	g_author = STR_AUTHOR; g_author += " "; g_author += STR_COIDGEN_VERSION;

	if( argc == 1 )
		return help();

	int arg_pos = 1;
    for( ; arg_pos < argc; arg_pos++ ) {
        if( argv[arg_pos][0] == '-' ) {
            if( argv[arg_pos][1] == '-' ) {
				if( strcmp(argv[arg_pos] + 2, "force") == 0 )
					b_force_generate = true;
				else if( strcmp(argv[arg_pos] + 2, "version") == 0 )
					return print_version();
				else if( strcmp(argv[arg_pos] + 2, "help") == 0 )
					return help();
			}
			else if( argv[arg_pos][1] == 'v' ) return print_version();
            else if( argv[arg_pos][1] == 'f' ) b_force_generate = true;
            else if( argv[arg_pos][1] == 'M' ) b_force_major = true;
            else if( argv[arg_pos][1] == 'N' ) b_force_minor = true;
            else {
				if( argv[arg_pos][1] == 'm' ) {
					b_keep_major = true;
					if( argv[arg_pos][1] == 'n' ) b_keep_minor = true;
				}
				else if( argv[arg_pos][1] == 'n' ) {
					b_keep_minor = true;
					if( argv[arg_pos][1] == 'm' ) b_keep_major = true;
				}
			}
        }
        else break;
    }

	if( b_force_minor && b_keep_minor ) {
		printf( "You can't use both -n and -N options !\n" );
		return 1;
	}
	if( b_force_major && b_keep_major ) {
		printf( "You can't use both -m and -M options !\n" );
		return 1;
	}


	g_source_header_path = argv[arg_pos];
	if( argc > arg_pos + 1 ) g_source_dev = argv[arg_pos + 1];


	sysDynamicLibrary * lib_dev = NULL;
	if( g_source_dev )
		lib_dev = new sysDynamicLibrary( g_source_dev );



	uint size;
	char * svc_header = read_file( g_source_header_path, &size );
	if( !svc_header ) {
		printf( "Cannot open file '%s'\n", g_source_header_path );
		return 2;
	}
	if( !size ) {
		printf( "Empty file: '%s'\n", g_source_header_path );
		return 3;
	}

	size_t source_size = 0;
	uint source_time = 0;
	struct stat source_stat;
	if( stat(g_source_header_path, &source_stat) == 0 ) {
		source_time = (uint)source_stat.st_mtime;
		source_size = source_stat.st_size;
	}





	const char char_bs = directory::separator();

	// change directory to the source service.h (==> all command line paths should be changed here)
	const char * p1 = strrchr( g_source_header_path, char_bs );
	if( p1 ) {
		*(char*)p1 = 0;
		directory::chdir( g_source_header_path );
		g_source_header_path = p1 + 1;
	}



	// parse command line
	// check directory structure (if needed)


	g_source_header_name0 = g_source_header_path;
    char * dot = strrchr( (char*)g_source_header_name0.ptr(), '.' );
	if( dot ) {
		*dot = 0;
		g_source_header_name0.correct_size();
	}


	// make client output directory
    directory::mkdir( STR_CLIENT_DIR_NAME, 0770 );

	OUTPUT_CLIENT_H = STR_CLIENT_DIR_NAME;
	OUTPUT_CLIENT_H += char_bs; OUTPUT_CLIENT_H += g_source_header_name0;
	OUTPUT_CLIENT_CPP = OUTPUT_CLIENT_H;
	OUTPUT_CLIENT_H += "_client.h"; OUTPUT_CLIENT_CPP += "_client.cpp";
	OUTPUT_DISPATCH_CPP = g_source_header_name0; OUTPUT_DISPATCH_CPP += "_dispatch.cpp";





	// read current (old) dispatch.cpp:
	H_FILE h_saved;
	size_t saved_size = UMAXS;
	uint saved_time = UMAX32;
	charstr saved_ver_coidgen, saved_ver_coid;
	//uint old_dispatch_size;
	//char * old_dispatch_header = read_file( OUTPUT_DISPATCH_CPP, &old_dispatch_size );
    bifstream old_dispatch(OUTPUT_DISPATCH_CPP);

	bool b_source_changed = true;
	int b_dispatch_read = 0;
	bool b_increment_major = false;

	if( old_dispatch.is_open() )
    {
		// read binary info about classes in previous service.h to detect changes
		b_dispatch_read = read_bin_dispatch_data( old_dispatch, false, h_saved, STR_COIDGEN_VERSION, saved_ver_coidgen, saved_ver_coid,
											&saved_size, &saved_time );
/*		b_dispatch_read == 1	==> all ok
		b_dispatch_read & 0x20	==> binary format is different
		b_dispatch_read & 0x10	==> major coidgen version changed
		b_dispatch_read == -1	==> crash
*/

		if( saved_size == source_size && saved_time == source_time ) {
			// source file's size and time match those stored in "*_dispatch.cpp" ==> nothing to do
			b_source_changed = false;
		}
	}

    old_dispatch.close();

	H_FILE h_client;
	h_client.header = h_saved.header;
	version & cur_version = h_client.header.ver;



	saved_size = source_size;
	saved_time = source_time;


	if( !old_dispatch.is_open() ) {
		cur_version.clear();
		b_source_changed = true;	// generate all
	}


	//delete [] old_dispatch_header;



	uint generate_new_version = 0;
	if( b_source_changed || b_force_generate || b_dispatch_read > 1 )
		generate_new_version = UMAX32;
	else {									// check if destination files exist
        if( !directory::is_valid_file(OUTPUT_CLIENT_H) )
            generate_new_version |= 1;

        if( !directory::is_valid_file(OUTPUT_CLIENT_CPP) )
            generate_new_version |= 2;

        if( !directory::is_valid_file(OUTPUT_DISPATCH_CPP) )
            generate_new_version |= 4;
	}



	if( generate_new_version == 0 ) {		/// nothing changed
#ifdef _DEBUG
		printf( "generate_new_version == 0, switching to UMAX32 (for debugging purposes)\n" );
		generate_new_version = UMAX32;
#endif
	}



	if( generate_new_version == 0 ) {		/// nothing changed
		printf( "Nothing to do\n" );
		return 0;
	}




	/// parse source header file
	h_client.source_header_name = g_source_header_path;
	h_client.client_header_name = strrchr( OUTPUT_CLIENT_H.ptr(), char_bs ) + 1;
	int ret1 = h_client.parse( NULL, svc_header, svc_header, svc_header + size );

	if( !h_client.contains_coid_include(svc_header) )
		printf( "Warning: include '%s' or you won't be able to compile your service header without errors\n", STR_coid_svc_header );

	delete [] svc_header;
	if( ret1 < 0 ) return 0x10;

	if( h_client.cls.size() == 0 ) {
		printf( "Error: no exported functions\n" );
		return 0x11;
	}





	/// read from .dev (if .dev path is given)
    binstreamconstbuf dev_data;
	//const char * dev_data = (const char *) NULL;

	H_FILE h_dev;
	charstr tmp;
	if( g_source_dev && lib_dev->is_valid() ) {
		void * fnc = lib_dev->getFuncAddress( STR_DEV_EXPORT_NAME );
		if( fnc ) {
			ClassRegister<ServiceDescriptor> * iface;
			iface = (ClassRegister<ServiceDescriptor> *) ((const void * (*)()) fnc)();
            typedef ClassRegister<ServiceDescriptor>::NodeClass     SDNodeClass;
			dynarray<SDNodeClass> el;
			uints n = iface->find_all( h_client.cls[0]->name, el );
			if(n) {
				const ServiceDescriptor * sd = &(*el[0]);
				if(sd)
					dev_data.set( sd->_bin_svc_data );
			}
		}
	}
    

	int b_dev_read = 0;
    if( dev_data.is_open() )
        b_dev_read = read_bin_dispatch_data( dev_data, true, h_dev, STR_COIDGEN_VERSION, tmp, tmp, NULL, NULL );

//	if( b_dev_read != 0 && b_dev_read != 1 )
//		b_increment_major = true;



	/// clean up .dev stuff
	//dev_data = NULL;
	delete lib_dev;









/*******************************************************************************************************
		////// VERSION CHECKING //////
*******************************************************************************************************/

	bool b_new_release = h_client.release_version != (const char *) cur_version._ver;
	if( h_client.release_version.len() ) {
		cur_version.set_string( h_client.release_version );
	}
	else cur_version.set_string( h_saved.release_version );

	if( b_dispatch_read <= 0 && b_dev_read == 1 ) {
		h_saved.ver_next_major = h_dev.ver_next_major;
		h_saved.header = h_dev.header;
	}


	uint v_next_major = 1, v_major = 0, v_minor = 0, v_build = 0;

	bool b_read_ver_from_dispatch = b_dispatch_read > 0 && b_dev_read != 1;

	if( b_read_ver_from_dispatch ) {
		v_next_major = h_saved.ver_next_major;
		v_major = h_saved.header.ver.get_major();
		v_minor = h_saved.header.ver.get_minor();
		v_build = h_saved.header.ver._build;
	}
	else if( b_dev_read > 0 ) {
		v_next_major = h_dev.ver_next_major;
		v_major = h_dev.header.ver.get_major();
		v_minor = h_dev.header.ver.get_minor();
		v_build = h_dev.header.ver._build;
	}
	if( b_dispatch_read > 0 ) v_build = h_saved.header.ver._build;


	if( b_dev_read > 0 ) {
		if( h_dev.ver_next_major > h_saved.ver_next_major )
			printf( "WARNING: your sources are older than .dev file !\n" );
	}




	int diff = 0;
	if( b_increment_major == false ) {
		if( b_dev_read == 1 ) {
			diff = h_client.compare( h_dev );
		}
		else if( b_dispatch_read == 1 ) {
			diff = h_client.compare( h_saved );
		}
		else {
			/// TODO: check coidgen version && increment major if coidgen.major_ver != stored.coidgen.major_ver

			if( old_dispatch.is_open() ) {			/// some errors occured while reading saved version data &&
				b_increment_major = true;				/// dispatch exists ==> increment major
				if( b_dev_read > 1 ) {
					printf( "Info: binary format of stored data in old .dev file has been changed, incrementing major version\n" );
				}
				else if( b_dispatch_read > 1 ) {
					printf( "Info: binary format of stored data in old dispatch file has been changed, incrementing major version\n" );
				}
			}
		}
	}


	if( b_increment_major || (diff & 0xFFFF0000) || b_force_major ) {
		if( !b_keep_major ) {
			v_major = v_next_major;
			v_minor = 0;
		}
		else {
			printf( "Keeping major version\n" );
			if( v_minor ) printf( "Warning: binary compatibility is probably broken !\n" );
		}
	}
	else if( (diff & 0xFFF0) || b_force_minor ) {
		if( !b_keep_minor ) v_minor++;
		else printf( "Keeping minor version\n" );
	}

	cur_version.set_major( v_major );
	cur_version.set_minor( v_minor );

	if( b_new_release ) cur_version._build = 1;
	else cur_version._build = v_build + 1;

	h_client.ver_next_major = cur_version.get_major() + 1;








/*******************************************************************************************************
		////// WRITE SECTION //////
*******************************************************************************************************/

	charstr out;
	h_client.header.author = g_author;
		//h_client.header.description		// read from service.h
	h_client.header.ver = cur_version;
	//h_client.header.date = ;											/// todo



	uint class_index = 0;
	h_client.cls[class_index]->write_init();
	h_client.cls[class_index]->sort_functions_by( 1 );		/// id




	/// write dispatch.cpp
	FILE * f = rename_and_open_file_for_writing( OUTPUT_DISPATCH_CPP );
	if(!f) return 2;

	/// stream binary data used for version checking
	out << h_client.header << EOL EOL;
	out << STR_OLD_HEADER_INFO;
	write_bin_dispatch_data( out, h_client, STR_COIDGEN_VERSION, COID_VERSION, source_size, source_time );
	out << STR_OLD_HEADER_END;

	h_client.write_dispatch_cpp( TEMPL_DISPATCH_CPP, out, class_index );
	fwrite( out.ptr(), out.len(), 1, f );
	fclose( f );






	/// write client.cpp
	out.reset();
	out << h_client.header << EOL EOL;
	h_client.write_client_cpp( TEMPL_CLIENT_CPP, out, class_index );

	f = rename_and_open_file_for_writing( OUTPUT_CLIENT_CPP );
	if(!f) return 2;
	fwrite( out.ptr(), out.len(), 1, f );
	fclose( f );





	/// write client.h
	h_client.class_members = TEMPL_CLIENT_H;
	h_client.header_info = h_client.class_members == NULL ? NULL : strstr( h_client.class_members, "$$$$" );
	if( h_client.header_info ) {*h_client.header_info = 0; h_client.header_info += 4;}

	out.reset();
	h_client.stream_to( out, T_client_header );

	h_client.class_members = NULL;
	h_client.header_info = NULL;

	f = rename_and_open_file_for_writing( OUTPUT_CLIENT_H );
	if(!f) return 2;
	fwrite( out.ptr(), out.len(), 1, f );
	fclose( f );




	/// write Makefile
	f = fopen( "Makefile", "rb" );
	if(!f) {
		f = fopen( "Makefile", "wb" );
		if(f) {
			out.reset();
			h_client.replace_template( true, TEMPL_MAKEFILE, out, h_client.cls[class_index] );
			fwrite( out.ptr(), out.len(), 1, f );
			fclose( f );
		}
	}
	else fclose( f );






	/// Done

	out.reset();
	cur_version.get_internal_version( out );
	printf( "New version is %s\n", out.ptr() );

	printf( "Done\n" );
//	Sleep( 200 );



	return 0;
}







namespace coid {












void H_FILE::add_block( CLASS * cls_parent, const char * base, char * from, char *& to, ushort flags )
{
	BLOCK * b;
	if( cls_parent ) {
		if( !flags ) return;
		b = cls_parent->blocks.add(1);
	}
	else b = blocks.add(1);

	while( *to == '\r' || *to == '\n' )
		to++;

	b->index_before_class = (ushort) cls.size();
	b->offset = uint(to - base);					/// store offset of the comment end
	b->text.add_from( from, to - from );
	b->flags = flags;
	/// look for NET_RELEASE: string
	char * p_release = strstr( (char*)b->text.ptr(), STR_coid_release );
	if( p_release ) {
		p_release += sizeof( STR_coid_release ) - 1;
		while( *p_release == ' ' || *p_release == '\t' ) p_release++;
		if( *p_release == '\"' ) p_release++;
		char * p_release_end = p_release;
		while( *p_release_end != 0 && *p_release_end != '\"' && *p_release_end != '\r' && *p_release_end != '\n' )
			p_release_end++;
		release_version.add_from( p_release, p_release_end - p_release );
	}


	memset( from, ' ', to - from );
	if( to - from >= 2 ) {						/// if this comment is in front of class ==> memset clears '\n' ==> class would contain bad offset
#ifdef WIN32
		from = (char *) to - 2;
		from[0] = '\r'; from[1] = '\n';
#else
		from = (char *) to - 1;
		from[0] = '\n';
#endif
	}
}





/// adds comments from 'ptr' to 'to', but only those that are in front of 'first_class'
	/// (do not parse the whole file because CLASS needs its comments ==> we give it the last comment index)
char * H_FILE::add_comments( CLASS * cls_parent, const char * base, char *& p_block_start,
										char * ptr, char * to, char * first_class )
{
	if( !first_class ) first_class = to;
	char * end = first_class < to ? first_class : to;
	bool class_is_within_comment = false;

	while(1) {
		char * pc = strchr( ptr, '/' );
		if( !pc || pc >= end ) break;
		if( pc[1] != '/' && pc[1] != '*' ) {
			ptr = pc + 1;
			continue;
		}

		uint sz = 2;
		const char * tmp = "*/";
		if( pc[1] == '/' ) {tmp = "\n"; sz = 1;}
		ushort flags = BLOCK::fnone;
		if( strncmp(pc, STR_client_only_L, sizeof(STR_client_only_L)-1) == 0 ) flags = BLOCK::fclient_only;
		else if( strncmp(pc, STR_client_L, sizeof(STR_client_L)-1) == 0 ) {flags = BLOCK::fadd_to_client; tmp = STR_client_R; sz = sizeof(STR_client_R) - 1;}
		else if( sz == 1 ) {
			const char * pc_end = strchr( pc, '\n' );
			const char * found = strstr( pc + 2, STR_coid_options );
			if( found && found < pc_end ) flags = 0;
			found = strstr( pc + 2, STR_coid_dependencies );
			if( found && found < pc_end ) flags = 0;
		}

		char * pc_end = strstr( pc, tmp );
		if( pc_end ) {
			pc_end += sz;
			if( flags ) add_block( cls_parent, base, pc, pc_end, flags );
			p_block_start = pc_end;
			ptr = pc_end;
		}
		else {
			if( flags & BLOCK::fadd_to_client )			/// "//}client" was not found
				printf( "Ignoring '//{client' block: cannot find end of block (//}client)\n" );
			ptr = pc + 1;
		}

		if( first_class > pc && first_class < pc_end )
			class_is_within_comment = true;
	}


	if( !class_is_within_comment ) return first_class;

	return ptr;
}






bool H_FILE::contains_coid_include( const char * ptr )
{
	while( 1 ) {
		const char * p = strstr( ptr, STR_include );
		if(!p) break;
		p += sizeof(STR_include) - 1;
		while( *p == ' ' || *p == '\t' || *p == '\"' )
			p++;
		if( strncmp(p, STR_coid_svc_header, sizeof(STR_coid_svc_header)-1) == 0 ) return true;
		//if( strncmp(p, STR_coid_svc_header2, sizeof(STR_coid_svc_header2)-1) == 0 ) return true;
		ptr = p;
	}

	return false;
}








int H_FILE::parse( CLASS * cls_parent, const char * base, char * from, char * to )
{
	static const char * p_cls_end_saved = NULL;
	bool class_to_export = false;
	if( !cls_parent ) {
		p_cls_end_saved = base;		/// + header.get_size
	}


	char * ptr = from;
	CLASS * new_class = new CLASS;
	char * p_cls_end = NULL;
	char * p_block_start = ptr;

	/// search for objects && comments
	while( ptr < to ) {
		char * p_obj = (char*)str2str( ptr, STR_class, STR_struct, sizeof(STR_struct) - 1 );
		char * p_comment = add_comments( cls_parent, base, p_block_start, ptr, to, p_obj );

        const char* ns = strstr(ptr, "namespace");
        if(ns>=to) ns=0;
        if(ns) ns += 10;
        if(ns)
            new_class->set_namespace(ns);

		if( p_obj != p_comment ) {
			ptr = p_comment; continue;
		}


		int tmp_size = sizeof(STR_class) - 1;
		if( *p_obj == 's' ) tmp_size = sizeof(STR_struct) - 1;
		if( (p_obj[tmp_size] != ' ' && p_obj[tmp_size] != '\t') ||
			(p_obj[-1] != ' ' && p_obj[-1] != '\t' && p_obj[-1] != '\n' && p_obj[-1] != '\r') )
		{
			ptr = p_obj + tmp_size; continue;
		}

		char * p_cls_start = (char*)str2chr( p_obj, ';', '{' );
		if( !p_cls_start ) break;

		// class p_obj followed by either ';' or '{' in p_cls_start
		if( *p_cls_start == ';' ) {
			ptr = p_cls_start + 1; continue;
		}

		// e.g.: class xxx : public Base {
		char * p2 = strchr( p_obj, ':' );
		if( p2 && p2 > p_cls_start ) p2 = NULL;

		// go to beginning of line
		char * p_start = p_obj;
		while( p_start > ptr && *p_start != '\n' ) p_start--;
		if( *p_start == '\n' ) p_start++;

		p_cls_end = str_count_chr( p_cls_start + 1, '{', '}', 1 );
		if( !p_cls_end ) {
			printf( "Cannot find end of class, check {} brackets inside (also in comments)\n" );
			delete new_class;
			return -2;
		}

		if( p_cls_end >= to ) break;

		// valid class definition ==> parse it
		new_class->flags = CLASS::fautonomous;
		if( *p_obj == 'c' ) p_obj += sizeof(STR_class) - 1;
		else {
			p_obj += sizeof(STR_struct) - 1;
			new_class->flags |= CLASS::fstruct;
		}
		new_class->parent = cls_parent;

		while( *p_obj == ' ' || *p_obj == '\t' || *p_obj == '\n' || *p_obj == '\r' )
			p_obj++;

		new_class->file = this;

		int ret = new_class->parse( base, p_start, p_obj, p_cls_start, p_cls_end );
		if( ret <= 0 ) {
			if( ret < 0 ) {
				delete new_class;
				return -1;
			}
			else {	/// add blocks from this class to global H_FILE::blocks ???
				new_class->fnc_export.reset();
			}
		}
		else if( ret ) {
			CLASS ** c = cls.add( 1 );
			*c = new_class;
			new_class = new CLASS;
			class_to_export = true;
			p_block_start = p_cls_end + 1;
			while( *p_block_start == ';' || *p_block_start == '\r' || *p_block_start == '\n' )
				p_block_start++;


			/// add disconnect function that must be present in every class:
			FUNCTION & fd = (*c)->fnc_disconnect;
			fd.parent = *c;
			fd.ret.type = "opcd"; fd.ret.flags = ARG::freturn; fd.ret.parent = &fd;
			fd.name = "disconnect";
			fd.id = 0;
            fd.flags = FUNCTION::fcomm_mask | FUNCTION::fnolog | FUNCTION::fmeta | FUNCTION::fdispatch_empty_body;
		}

		ptr = p_cls_end + 1;
	}

	delete new_class;


	return class_to_export != 0;
}









int CLASS::parse( const char * base, char * p_start, char * p_name, char * p_cls_start, char * p_cls_end )
{
	/// reset class
	blocks.reset();
	class_text_offset = p_start - base;
	class_text_offset_end = p_cls_end - base;
	fnc_index = 0;
	for( uint i=0; i<SPEC_FNC_COUNT; i++ ) {
		if( fnc_special[i] ) {
			delete fnc_special[i]; fnc_special[i] = NULL;
		}
	}



	char l1 = *p_cls_start;
	*p_cls_start = 0;
	size_t offset = strcspn( p_name, " \t:\r\n" );
	char * p = p_name + offset;
	char l2 = *p;
	*p = 0;

	if( parent ) {
		full_name = parent->full_name; full_name += "::";
	}
	name = p_name;
	full_name += name;
	*p = l2;
	char l3 = *p_name;
	*p_name = 0;

	name_left = str_trim( p_start );
	name_right = str_trim( p );

	*p_name = l3;
	*p_cls_start = l1;

	char l_cls_end = *p_cls_end;
	*p_cls_end = 0;			/// faster searching

	int count = file->parse( this, base, p_cls_start, p_cls_end );	/// parse classes inside our
	if( count < 0 ) {*p_cls_end = l_cls_end; return count;}		/// < 0 ==> error, == 0 ==> none inline classes


	/// fill options && flags && dependencies
	char * p_opt;
	if( (p_opt=strstr(p_cls_start, STR_coid_options)) ) {
		p_opt += sizeof(STR_coid_options) - 1;
		char * p2 = strchr( p_opt, '\n' );
		if( p2 ) *p2 = 0;
		if( p2[-1] == '\r' ) p2[-1] = 0;
		options = p_opt;
		for( uint i=0; i<STR_options_size; i++ ) {
			const char * p = strstr( p_opt, STR_options[i].name );
			if(p) {
				ints len = strlen( STR_options[i].name );
				if( p[len] == 0 || p[len] == '\r' || p[len]== '\n' || p[len] == ' ' || p[len] == '\t' ||
					p[len] == ',' || p[len] == ';' )
				{
					flags &= STR_options[i].flags_and;
					flags |= STR_options[i].flags;
					if( flags & CLASS::foption_read_info ) {
						if( i == 0 ) {		/// port number
							acceptor_port = atoi( p + len );
							if( acceptor_port == 0 ) {}	/// not specified ==> port number will be read from config file
						}
					}
					else if( flags & CLASS::facceptor_bound_session ) {
						if( acceptor_port == UMAX32 ) {
							printf( "Warning: option 'acceptor_bound_session' may be used only together with acceptor_port\n" );
							flags &= CLASS::facceptor_bound_session;
						}
					}
				}
			}
		}
		if( p2 ) *p2 = '\n';
		if( p2[-1] == 0 ) p2[-1] = '\r';
	}
	if( !(flags & fallow_access_mask) ) flags |= fallow_access_mask;

	if( (p_opt=strstr(p_cls_start, STR_coid_dependencies)) && p_opt < p_cls_end ) {
		p_opt += sizeof(STR_coid_dependencies) - 1;
		char * p2 = strchr( p_opt, '\n' );
		if( p2 ) *p2 = 0;
		while( *p_opt == ' ' || *p_opt == '\t' ) p_opt++;
		dependencies = p_opt; dependencies.trim();
		if( p2 ) *p2 = '\n';
	}


	/// parse body
	p_cls_start++;
	char * ptr = p_cls_start;

	while( 1 ) {
		p = strstr( ptr, STR_net_fnc );
		if(!p) break;

		FUNCTION ** f = fnc_export.add( 1 );
		*f = new FUNCTION;
		(*f)->parent = this;

		ptr = (*f)->parse( base, p );		/// p is '\0' terminated at the end of class

		if( !ptr ) {
			fnc_export.del( f - fnc_export.ptr() );
			delete *f;
			return -1;
		}

		if( (*f)->flags & FUNCTION::fspecial_fnc ) {
			(*f)->id = UMAX32;
		}
		else {
			(*f)->id = (uint)fnc_index++;
			if( flags & fnolog ) (*f)->flags |= FUNCTION::fnolog;
		}

		DASSERT( STR_special_functions_size == SPEC_FNC_COUNT );
		if( (*f)->flags & FUNCTION::fspecial_fnc ) {		/// move it to 'fnc_special'
			/// find index to 'fnc_special'
			uint i;
			for( i=0; i < STR_special_functions_size; i++ ) {
				if( strcmp((*f)->name.ptr(), STR_special_functions[i].name) == 0 ) {
					DASSERT( i == STR_special_functions[i].flags );
					break;
				}
			}

			/// if the name cannot be recongnized ==> leave with error
			if( i >= STR_special_functions_size ) {
				printf( "Function %s was not recognized as special function !\n", (*f)->name.ptr() ); return -1;
			}

			/// move
			fnc_special[i] = *f;
			uints fnc_index = f - fnc_export.ptr();
			fnc_export.del( fnc_index );
		}
	}


	//bool class_ok = fnc_index > 1;// || count;
	bool class_ok = fnc_index > 0;// || count;
	main_comment.reset();	/// this class may have been used already
    if( class_ok )
		fill_comment( main_comment, class_text_offset, true );


	*p_cls_end = l_cls_end;
	memset( p_start, ' ', p_cls_end + 2 - p_start ); /// p_cls_end should be   "};..."


	if( !class_ok ) {				/// accept class even if only special functions are present
		for( uint i=0; i<SPEC_FNC_COUNT; i++ ) {
			if( fnc_special[i] ) {class_ok = true; break;}
		}
	}


	if( class_ok ) {
		if( !check_functions() ) return -1;
		sort_functions_by( 0 );		/// name
	}



	return class_ok;
}






bool CLASS::check_functions()
{
/*		/// check for destructor
	uint destr = UMAX32;
	for( uint i=0; i<fnc_export.size(); i++ ) {
		if( fnc_export[i]->name.len() > 1 && fnc_export[i]->name[0] == '~' ) {
			if( strcmp((const char *) fnc_export[i]->name + 1, name) ) {
				destr = i; break;
			}
		}
	}

	if( destr != UMAX32 ) {				/// remove
		delete fnc_export[destr];
		fnc_export.del( destr );
	}
*/


	if( max_fnc_index < fnc_export.size() )
		max_fnc_index = fnc_export.size();		/// may change when we'll compare this class with the old version


	/// special functions:
	for( uint i=0; i < STR_special_functions_size; i++ ) {
		DASSERT( i == STR_special_functions[i].flags );
		FUNCTION * f = fnc_special[i];
		if( i <= accept_connect_shared && f == NULL ) {		/// these methods must be present
			f = fnc_special[i] = new FUNCTION;
			f->parent = this; f->name = STR_special_functions[i].name;
			f->flags = FUNCTION::fspace_in_brackets | FUNCTION::fdispatch_empty_body | FUNCTION::fspecial_fnc | FUNCTION::fnot_present_in_service;
			f->ret.type = STR_connect_ret_val; f->ret.parent = f; f->ret.flags = ARG::freturn;
		}

		if(f) {
			switch( i ) {
				/// 3 types of connect have user-specific arguments ==> don't check for anything
				case accept_connect:
					f->id = StdDispatchMethod::AUTHENTIFY;
					if( !f->opcd_return_type() ) {
						printf( "Error in function accept_connect(): return type should be 'opcd'\n" );
						return false;
					}
				break;
				case accept_connect_shared:
					f->id = StdDispatchMethod::AUTHENTIFY_SHARED;
					if( !f->opcd_return_type() ) {
						printf( "Error in function accept_connect_shared(): return type should be 'opcd'\n" );
						return false;
					}
				break;
				case accept_connect_auto:
					flags |= CLASS::fautorun;
					f->id = StdDispatchMethod::AUTHENTIFY_AUTO;
					if( !f->opcd_return_type() ) {
						printf( "Error in function accept_connect_auto(): 'opcd' return type expected\n" );
						return false;
					}
					if( !f->none_arguments() ) {
						printf( "Error: bad accept_connect_auto() function prototype, should be %s();\n", STR_special_functions[i].name );
						return false;
					}
				break;
				case accept_net_connections: {
					f->id = StdDispatchMethod::HANDLE_NET_LOOP;
					if( acceptor_port > 0xFFFF )
						acceptor_port = 0;	/// tell coid to read port number from config

					if( !f->opcd_return_type() ) {
						printf( "Error in function accept_net_connections(): 'opcd' return type expected\n" );
						return false;
					}
					/// check for the second argument type ? (int/short/uint/unsigned int/UINT/uint/..............)
					bool err = f->args.size() != 2  ||  f->args[0].type != "netSocket"  ||  f->args[0].asterix_count != 1  ||
								f->args[1].asterix_count != 0;
					if( err ) {
						printf( "Error in function accept_net_connections(): bad function prototype, should be accept_net_connections( netSocket * s, uint port )\n" );
						return false;
					}
				} break;
				case accept_destroy:
					f->id = StdDispatchMethod::PREDESTRUCTOR;		/// return type is ignored
					if( !f->none_arguments() ) {
						printf( "Error: bad function prototype, should be %s();\n", STR_special_functions[i].name );
						return false;
					}
				break;
				case accept_startup_params:
					f->id = 0x8000FFFF;
					if( f->args.size() != 1 || f->args[0].type.len()==0 || f->args[0].asterix_count ||
						! (f->args[0].flags&ARG::fref) || f->args[0].type != STR_cmd_interface )
					{
						printf( "Error in function '%s': bad prototype, should be %s( %s & cmd );\n", f->name.ptr(), STR_special_functions[i].name, STR_cmd_interface );
						return false;
					}
				break;
				case accept_command:
					f->id = StdDispatchMethod::COMMAND;
					// todo: check parameters
					if( !f->opcd_return_type() ) {
						printf( "Error in function accept_command(): 'opcd' return type expected\n" );
						return false;
					}
					if( f->args.size() != 1 || f->args[0].type.len()==0 || f->args[0].asterix_count ||
						! (f->args[0].flags&ARG::fref) || f->args[0].type != STR_command_tokens )
					{
						printf( "Error in function '%s': bad prototype, should be 'opcd accept_command( %s & params );'\n", f->name.ptr(), STR_command_tokens );
						return false;
					}

				break;
				case accept_spawn:
					flags &= ~fautonomous; flags |= CLASS::fhas_spawn_point;
					f->id = StdDispatchMethod::SPAWN;
					if( !f->none_arguments() ) {
						printf( "Error: bad function prototype, should be %s();\n", STR_special_functions[i].name ); return false;
					}
				break;
				case accept_echo:
					f->id = StdDispatchMethod::ECHO;
					if( !f->none_arguments() ) {
						printf( "Error: bad function prototype, should be %s();\n", STR_special_functions[i].name ); return false;
					}
				break;
				default: DASSERT( 0 ); break;
			}
		}
	}



	return true;
}



void CLASS::write_init()
{
	/// fill dispatch function that must be present in every class:
	fnc_dispatch.parent = this;
	fnc_dispatch.ret.type = "opcd"; fnc_dispatch.ret.flags = ARG::freturn; fnc_dispatch.ret.parent = &fnc_dispatch;
	/// fill name:
	file->replace_template( true, "COID_%name%_%_version_%_dispatch", fnc_dispatch.name, this );
	fnc_dispatch.id = 0;
	fnc_dispatch.flags = FUNCTION::fdispatch_empty_body;

	ARG * a = fnc_dispatch.args.add( 3 );
	a->asterix_count = 1; a->flags = ARG::fptr; a->type = "InstanceData"; a->name = "_coid_inst_data"; a++;
	a->flags = 0; a->type = "uint"; a->name = "_coid_fnc_id"; a++;
	a->flags = ARG::fref; a->type = "binstream"; a->name = "_coid_bstr";
}



void FUNCTION::get_id_name( charstr & id_name ) const
{
	id_name = "COID_";
	if( parent ) {
		id_name += parent->name; id_name += "_";
	}
	id_name += name; id_name += "_";
	id_name += (ushort) id;
	replace_char( (char*)id_name.ptr(), ':', '_' );
}





bool FUNCTION::none_arguments() const
{
	if( args.size() == 0 ) return true;
	if( args.size() > 1 ) return false;
	return (args[0].flags & ARG::fvoid) != 0;
}



bool FUNCTION::void_return_type() const
{
	return (ret.flags & ARG::fvoid) != 0;
}



bool FUNCTION::opcd_return_type() const
{
	return ret.type == "opcd" && ! (ret.flags & (ARG::fref | ARG::fptr));
}



const ARG * FUNCTION::get_account_arg() const
{
	for( uint i=0; i<args.size(); i++ )
		if( args[i].flags & ARG::faccount ) return &args[i];
	return NULL;
}






char * FUNCTION::parse( const char * base, char * ptr )
{
	/// check for additional flags
	ptr += sizeof(STR_net_fnc) - 1;
	if( *ptr == '_' ) {
		ptr++;
		if( *ptr == 's' ) {flags |= fspecial_fnc; ptr++;}
		for( uint i=0; i<1; i++ ) {
			if( ptr[i] == 'd' ) {flags |= fdirect; ptr++;}
			else if( ptr[i] == 'i' ) {flags |= finterprocess; ptr++;}
			else if( ptr[i] == 'r' ) {flags |= fremote; ptr++;}
		}
	}

	if( !(flags & fcomm_mask) ) flags |= fcomm_mask;


	char * p_arg_start = strchr( ptr, '(' );
	if( !p_arg_start ) return ptr + 1;


	char * p_nolog = strstr( ptr, STR_net_nolog );
	if( p_nolog && p_nolog < p_arg_start ) {
		char * ptr2 = p_nolog + sizeof(STR_net_nolog) - 1;
		if( ptr2 > ptr ) ptr = ptr2;
		flags |= fnolog;
	}

	char * p_meta = strstr( ptr, STR_net_meta );
	if( p_meta && p_meta < p_arg_start ) {
		char * ptr2 = p_meta + sizeof(STR_net_meta) - 1;
		if( ptr2 > ptr ) ptr = ptr2;
		flags |= fmeta;
	}

	char * p_nolock = strstr( ptr, STR_net_nolock );
	if( p_nolock && p_nolock < p_arg_start ) {
		char * ptr2 = p_nolock + sizeof(STR_net_nolock) - 1;
		if( ptr2 > ptr ) ptr = ptr2;
		flags |= fnolock;
	}


	*p_arg_start = 0;						/// try to find function name
	char * p = p_arg_start - 1;
	while( p > ptr && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') ) {
		*p-- = 0;
		flags |= fspace_after_name;
	}

	char * p_name = p;
	while( p_name > ptr && (*p_name != ' ' && *p_name != '\t' && *p_name != '\r' && *p_name != '\n') )
		p_name--;

	if( p_name >= ptr ) p_name++;

	p = strstr( p_name, "::" );
	if(p) {*p_name = 0; name = p + 2;}
	else {name = p_name; *p_name = 0;}




	/// find special function
	if( flags & FUNCTION::fspecial_fnc ) {
		uint i;
		for( i=0; i < STR_special_functions_size; i++ ) {
			if( name == STR_special_functions[i].name ) {
				DASSERT( i == STR_special_functions[i].flags );
				break;
			}
		}

		/// if the name cannot be recongnized ==> leave with error
		if( i >= STR_special_functions_size ) {
			printf( "Function %s was not recognized as special function !\n", name.ptr() );
			return NULL;
		}

		if( i == CLASS::accept_net_connections )
			flags |= fkeep_as_is;		/// do not check in/out.... arguments, it will be done in CLASS::check_functions()
	}


	parent->fill_comment( main_comment, ptr - base, false );


	p = str_trim( ptr );
	ret.parent = this;
	ptr = p_arg_start + 1;
	if( *ptr == ' ' || *ptr == '\t' )
		flags |= fspace_in_brackets;

	char * p_ret_ok = ret.parse( p, true );
	if( ret.name.len() ) {
		charstr tmp = ret.name; tmp += " "; tmp += name; name.takeover( tmp );
		ret.name.reset();
	}

	if( !p_ret_ok ) return NULL;



	char * p_fnc_end = str_count_chr( ptr, '(', ')', 1 );
	bool count_search = false;

	while( 1 ) {
		const ints max_len = p_fnc_end - ptr;
		token tok( ptr, max_len );
		int br_count = 0, lg_count = 0;
		ints offset = -1;
		while( 1 ) {
			offset++;
			offset = tok.count_notingroup( ",<>()", offset );
			if( offset >= max_len ) {
				DASSERT( offset == max_len );
				if( br_count || lg_count ) {
					printf( "Error parsing function '%s': error occured in '%10s'...\n", name.ptr(), ptr );
					return NULL;
				}
				break;
			}
			if( tok[offset] == ',' ) {
				if( br_count || lg_count ) continue;
				break;
			}

			if( tok[offset] == '(' ) br_count++;
			else if( tok[offset] == ')' ) br_count--;
			else if( tok[offset] == '<' ) lg_count++;
			else if( tok[offset] == '>' ) lg_count--;
		}

		bool last = offset == max_len;
		p = ptr + offset;
		*p = 0;
		ptr = str_trim( ptr );
		if( *ptr == 0 ) {ptr = p + 1; break;}			/// no arguments

		ARG * a = args.add( 1 );
		a->parent = this;
		a->flags = 0;
		ptr = a->parse( ptr, false );
		if( !ptr ) {
			args.del( args.size() - 1 );
			return NULL;
		}
		if( a->flags & ARG::fvoid ) args.del( args.size() - 1 );
		if( a->flags & ARG::fptr_count ) count_search = true;
		if( !(flags & fspecial_fnc) ) {
			if( a->is_remapped() ) handle_client_calls = 1;
			else {
				if( handle_client_calls == 0 && (a->flags & ARG::ffetch) )
					handle_client_calls = 2;
			}
		}

		ptr = p + 1;
		if( last )
			break;
	}

	p = ptr;
	while( *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' ) p++;
	if( strncmp(p, "const", sizeof("const")-1 ) == 0 )
		flags |= fconst;


	if( count_search ) {		/// search for arguments that specify count for pointers, + error checking
		for( uint i=0; i<args.size(); i++ ) {
			if( !(args[i].flags & ARG::fptr_count) ) continue;
			ARG * ptr = &args[i];
			DASSERT( ptr->arg_count == UMAX32 );

            for( uint j=0; j<args.size(); j++ ) {
				if( strstr(ptr->count_string.ptr(), args[j].name.ptr()) ) {
					if( ptr->arg_count != UMAX32 ) {
						printf( "Error in argument '%s': only one member can be specified as a count for a pointer\n", ptr->name.ptr() );
						return NULL;
					}
					ptr->arg_count = j;
					break;
				}
			}

			DASSERT( args[i].count_string.len() );

			/// add special arg, that will be responsible for streaming pre-evaluated count (just name and type)
			args[i].arg_count_eval = args[i].create_arg_count_eval();

			/// check for count argument and verify some requirements
			if( ptr->arg_count != UMAX32 )  {
				ARG * count = &args[ptr->arg_count];
				count->flags |= ARG::fcount;
				if( (count->flags & (ARG::fout | ARG::ffetch)) && (ptr->flags & (ARG::fin | ARG::finout)) ) {		/// count is uknown on client side !!!
					printf( "Error in function %s: argument '%s' is an output argument, but pointer (%s) is 'net_in' !\n", name.ptr(), count->name.ptr(), ptr->name.ptr() );
					return NULL;
				}
			}
		}
	}


	return ptr;
}






char * ARG_ERROR( const char * what, const char * fnc_name, const char * arg_name )
{
	printf( what, fnc_name, arg_name );
	return NULL;
}





char * ARG::parse( char * ptr, bool is_return_type )
{
	if( is_return_type ) {
		flags |= freturn;
		char * p = ptr;

		for( uint i=0; i<STR_remove_types_size; i++ ) {
			p = strstr( ptr, STR_remove_types[i].name );
			if(p) {
				bool check = (STR_remove_types[i].flags & NAME_FLAGS::ferror) && ! (parent->flags & FUNCTION::fkeep_as_is);
				if( check ) {
					type = ptr;
					return ARG_ERROR( "Error in function '%s': return value ('%s') contains keywords that are not supported\n", parent->name.ptr(), name.ptr() );
				}

				if( STR_remove_types[i].flags & NAME_FLAGS::fappend_name ) {
					name = STR_remove_types[i].name;
				}

				if( check ) remove_part_of_string( p, p + strlen(STR_remove_types[i].name) );
			}
		}

		/// find * and &
		char * ptr2 = ptr + strlen( ptr ) - 1;
		while( ptr2 > ptr && (*ptr2 == ' ' || *ptr2 == '\t' || *ptr2 == '\r' || *ptr2 == '\n') )
			ptr2--;
		ptr2[1] = 0;
		while( ptr2 > ptr && (*ptr2 == '*' || *ptr2 == '&' || *ptr2 == ' ' || *ptr2 == '\t' || *ptr2 == '\r' || *ptr2 == '\n') )
			ptr2--;

		if( *ptr2 && (*ptr2 != '*' || *ptr2 != '&') )
			ptr2++;


		while( 1 ) {
			p = ptr2 + strcspn( ptr2, "*&" );
			if( !*p ) break;
			if( *p == '*' ) {
				if( asterix_count == 0 ) {
					char * p2 = strstr( ptr, "char" );
					if( p2 && (p2[-1] == ' ' || p2[-1] == '\t') && (p2[4] == ' ' || p2[4] == '\t' || p2[4] == '*' || p2[4] == '&' ) ) {
						flags |= fstring;
					}
				}
				flags |= fptr;
				asterix_count++;
			}
			else {
				flags |= fref;
			}
			*p = ' '; 
			ptr2 = p + 1;
		}

		type = str_trim( ptr );

		if( (parent->flags & FUNCTION::fcomm_mask) != FUNCTION::fdirect ) {
			if( (flags & fstring) ) {
				if( asterix_count > 1 ) {
					printf( "Error in function '%s': too many '*' in return value\n", parent->name.ptr() );
					return NULL;
				}
			}
			else if( asterix_count != 0 ) {
				printf( "Error in function '%s': pointer in return value is not supported (except strings)\n", parent->name.ptr() );
				return NULL;
			}
		}
	}
	else {
		/// default parameter
		char * def = strchr( ptr, '=' );
		if( def ) {
			char * before = def - 1;
			while( *before == ' ' || *before == '\t' ) *before-- = 0;
			*def++ = 0;
			while( *def == ' ' || *def == '\t' ) *def++ = 0;
			def_value = def;
		}
		
		/// name
		if( !name.len() ) {
			char * p = ptr + strlen( ptr ) - 1;
			p = prev_word( ptr, p );
			if( strcmp(p, STR_void) != 0 ) {
				name = p;
				*p = 0;
			}
		}


		charstr type_remap_buf;
		if( !flags ) flags |= fin;
		char * ptr2 = ptr;
		uint affected_flags = 0;

		while( 1 ) {
			char * p = strstr( ptr2, "net_" );
			if(!p) break;

			for( uint i=0; i<g_ARG_keywords_size; i++ ) {
				if( strncmp(p, g_ARG_keywords[i].name, g_ARG_keywords[i].size) == 0 ) {
					if( i <= 3 ) {
						char * p2 = str_count_chr( p + g_ARG_keywords[i].size, '(', ')', 1 );
						if( !p2 ) {
							printf( "Error parsing %s(expr.) '%s' in function '%s'\n", g_ARG_keywords[i].name, name.ptr(), parent->name.ptr() );
							return NULL;
						}
						char * p3 = p + g_ARG_keywords[i].size - 1;
						if( i == 0 ) {			//net_count( xxx )
							flags &= g_ARG_keywords[i].flags_and;
							flags |= g_ARG_keywords[i].flags;
							affected_flags |= g_ARG_keywords[i].flags;
							count_string.add_from( p3, p2 + 1 - p3 );
						}
						else if( i == 1 ) {			//net_openservice
							if( parent->open_service.len() ) {
								printf( "Error in argument '%s' in function '%s': you cannot open more than 1 service within a single call\n", name.ptr(), parent->name.ptr() );
								return NULL;
							}
							flags &= g_ARG_keywords[i].flags_and;
							flags |= g_ARG_keywords[i].flags;
							affected_flags |= g_ARG_keywords[i].flags;
							p3++;
							while( *p3 == ' ' || *p3 == '\t' ) p3++;
							parent->open_service.add_from( p3, p2 - p3 );
							parent->open_service.trim();
						}
						else {
							if( type_remap_buf.len() )	/// check
								return ARG_ERROR( "Error in function '%s': net_remap and net_map used together ! (argument '%s')\n", parent->name.ptr(), name.ptr() );
							p3++;
							type_remap_buf.add_from( p3, p2 - p3 );
							if( i == 2 ) {}			//net_remap
							else if( i == 3 )		//net_map
								flags |= fmapped;
						}
						memset( p, ' ', p2 + 1 - p );
						break;
					}
					else {
						flags &= g_ARG_keywords[i].flags_and;
						flags |= g_ARG_keywords[i].flags;
						affected_flags |= g_ARG_keywords[i].flags;
						affected_flags |= ~g_ARG_keywords[i].flags_and;
					}
					memset( p, ' ', g_ARG_keywords[i].size );
					break;
				}
			}
			ptr2 = p + 1;
		}

		/// find * and &
		ptr2 = ptr + strlen( ptr ) - 1;
		while( ptr2 > ptr && (*ptr2 == ' ' || *ptr2 == '\t' || *ptr2 == '\r' || *ptr2 == '\n') )
			ptr2--;
		ptr2[1] = 0;
		while( ptr2 > ptr && (*ptr2 == '*' || *ptr2 == '&' || *ptr2 == ' ' || *ptr2 == '\t' || *ptr2 == '\r' || *ptr2 == '\n') )
			ptr2--;

		if( *ptr2 && (*ptr2 != '*' || *ptr2 != '&') )
			ptr2++;


		while( 1 ) {
			char * p = ptr2 + strcspn( ptr2, "*&" );
			if( !*p ) break;
			if( *p == '*' ) {
				if( asterix_count == 0 ) {
					char * p2 = strstr( ptr, "char" );
					if( p2 && (p2 == ptr || p2[-1] == ' ' || p2[-1] == '\t') && (p2[4] == ' ' || p2[4] == '\t' || p2[4] == '*' || p2[4] == '&' ) ) {
						flags |= fstring;
					}
				}
				flags |= fptr;
				asterix_count++;
			}
			else {
				flags |= fref;
			}
			*p = ' '; 
			ptr2 = p + 1;
		}


		type = str_trim( ptr );
		if( strstr(type.ptr(), STR_const) ) flags |= fconst;

		// check params
		if( (parent->flags & FUNCTION::fkeep_as_is) == 0 && (parent->flags & FUNCTION::fcomm_mask) != FUNCTION::fdirect ) {
			if( flags & fopen_service ) {
				if( (flags & (fin | fout | ffetch | fconst | faccount | fvoid | fptr | fref)) != fref ) {
					return ARG_ERROR( "Error in function '%s', argument '%s' must be declared as follows:\nnet_openservice(a_service_name) COID_OPEN_SERVICE & a_variable_name\n", parent->name.ptr(), name.ptr() );
				}
			}


			/// net_ptr keyword hides 1 asterix
			if( flags & fnet_ptr ) {
				if( asterix_count == 0 ) {
					return ARG_ERROR( "Error in function '%s', argument '%s' is marked as net_ptr, but argument is not a pointer\n", parent->name.ptr(), name.ptr() );
				}
				asterix_count--;
				type += " *";
			}

			int tmp_asterix_count = asterix_count + ((flags & fref) ? 1 : 0);

			if( flags & faccount ) {
				if( !(parent->flags & FUNCTION::fspecial_fnc) ||
					(parent->name != STR_special_functions[0].name && parent->name != STR_special_functions[1].name) )
				{
					ARG_ERROR( "Warning in function '%s' in argument '%s': 'net_account' keyword can be used only in accept_connect[_shared] methods\n", parent->name.ptr(), name.ptr() );
					flags &= faccount;
				}
				if( type != STR_account_id )
					return ARG_ERROR( "Error in function '%s', argument '%s' is marked as net_account parameter, but its type is wrong\n", parent->name.ptr(), name.ptr() );
				if( tmp_asterix_count != 1 )
					return ARG_ERROR( "Error in function '%s', prototype of account parameter (argument '%s') must be 'account_id & any_name'\n", parent->name.ptr(), name.ptr() );
				flags &= ~(fin | fout | finout | ffetch);
			}

			if( flags & fin ) {
				if( (flags & fstring) ) {
					if( tmp_asterix_count > 1 )
						return ARG_ERROR( "Error in function '%s': too many '*' in argument '%s'\n", parent->name.ptr(), name.ptr() );
				}
				else {
					if( tmp_asterix_count > 1 || (tmp_asterix_count == 1 && ! (flags & (fptr_count | fref))) )
						return ARG_ERROR( "Error in function '%s': net_in argument '%s' should be either reference or pointer with count\n", parent->name.ptr(), name.ptr() );
				}
			}
			else if( flags & (finout | fout) ) {
				if( !(flags & (fptr | fref)) )
					return ARG_ERROR( "Error in function '%s': output argument '%s' cannot obtain output data\n", parent->name.ptr(), name.ptr() );
				else if( tmp_asterix_count > 2 )								/// e.g. "net_out int **&"
					return ARG_ERROR( "Error in function '%s': too many '*' in argument '%s'\n", parent->name.ptr(), name.ptr() );
				else if( tmp_asterix_count == 2 && ! (flags & (fptr_count | fstring)) )		/// e.g. "net_out int **"
					return ARG_ERROR( "Error in function '%s': use count() macro in argument '%s'\n", parent->name.ptr(), name.ptr() );
				else if( tmp_asterix_count == 1 && (flags & fptr_count) )		/// e.g. "net_out net_count() int *"
					return ARG_ERROR( "Error in function '%s': argument '%s' should be without 'net_count' (it points to 1 element)\n", parent->name.ptr(), name.ptr() );
				else if( flags & fconst )
					return ARG_ERROR( STR_ERR_const, parent->name.ptr(), name.ptr() );
			}
			else if( flags & ffetch ) {
				if( !parent->opcd_return_type() )
					return ARG_ERROR( "Error in function '%s': return type must be 'opcd' if there is a net_fetch argument ('%s')\n", parent->name.ptr(), name.ptr() );
				else if( tmp_asterix_count != 2 )
					return ARG_ERROR( "Error in function '%s': net_fetch argument '%s' must be declared with '*&' or '**'\n", parent->name.ptr(), name.ptr() );
			}
		}

		handle_default_flags( affected_flags );

		if( type_remap_buf.len() ) {
			type_remap = new ARG;
			type_remap->parent = parent;
			type_remap->flags = flags & f_typeremap_inherit;
			if( flags & fmapped ) type_remap->name = "mapped_";
			else type_remap->name = "remapped_";
			type_remap->name += name;
			if( !type_remap->parse((char*)type_remap_buf.ptr(), false) )
				return NULL;
		}
	}

	if( type == STR_void && asterix_count == 0 ) flags |= fvoid;



	return ptr;
}






bool ARG::is_localarray() const
{
	int tmp_asterix_count = asterix_count + ((flags & ARG::fref) ? 1 : 0);
	bool b_localarray = false;

	if( (flags & (ARG::fstring | ARG::fptr_count)) == ARG::fptr_count ) {
		if( flags & ARG::fin ) {
			DASSERT( asterix_count <= 1 && tmp_asterix_count <= 1 );
			if( asterix_count ) b_localarray = true;
		}
		else if( flags & (ARG::finout | ARG::fout) ) {
			DASSERT( asterix_count <= 2 && tmp_asterix_count <= 2 );
			if( tmp_asterix_count == 2 ) b_localarray = true;
		}
	}

	return b_localarray;
}



bool ARG::is_array() const
{
	int tmp_asterix_count = asterix_count + ((flags & ARG::fref) ? 1 : 0);
	if( (flags & fptr_count) && tmp_asterix_count == 2 )
		return true;

	return is_localarray();
}



bool ARG::stream_array( charstr& out ) const
{
	if( !is_array() ) return false;

	out << "write_array_ptr( ";		/// we suppose that the stream name is present in 'out'
	if( flags & ffetch ) append_dispatch_arg_name( out, adt_stream_out );
	else {
		append_dispatch_arg_name( out, adt_name );
		out << ".get_ptr()";
	}
	out << ", (uint)" << count_string << " );";

	return true;
}



void ARG::handle_default_flags( uint mask )
{
	mask = ~mask;	/// skip flags which were set by user

	if( is_array() )
		flags |= (fnolog & mask);
	else
		flags &= ~(fnolog & mask);
}




ints CLASS::fill_comment( charstr& comment, uints offset, bool use_parent )
{
	dynarray<BLOCK> & b = use_parent ? (parent ? parent->blocks : file->blocks) : blocks;
	if( !b.size() ) return -1;

	uints max_offset = UMAXS;
	for( uints i=0; i<b.size(); i++ ) {
		if( b[i].offset <= offset && b[i].text.len() && b[i].flags & BLOCK::fnone ) {
			if( max_offset == UMAXS || b[max_offset].offset < b[i].offset )
				max_offset = i;
		}
	}

	if( max_offset != UMAXS && offset - b[max_offset].offset < 30 ) {
		comment.takeover( b[ max_offset ].text );
		b[ max_offset ].flags = 0;
		return max_offset;
	}

	return -1;
}


} //namespace coid




